package org.coco.mvc;

import lombok.Getter;
import org.coco.core.BeanContainer;
import org.coco.core.annotation.Controller;
import org.coco.core.annotation.RequestMapping;
import org.coco.inject.DependencyInjector;
import org.coco.mvc.handle.HandleMapping;
import org.coco.mvc.processor.RequestProcessor;
import org.coco.mvc.processor.impl.ControllerRequestProcessor;
import org.coco.mvc.processor.impl.JspRequestProcessor;
import org.coco.mvc.processor.impl.PreRequestProcessor;
import org.coco.mvc.processor.impl.StaticRequestProcessor;
import org.coco.util.ValidationUtil;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@WebServlet("/*")
public class DispatcherServlet extends HttpServlet {

    private BeanContainer beanContainer;

    private DependencyInjector dependencyInjector = new DependencyInjector();

    private Map<String,HandleMapping> urlAndPathMap = new ConcurrentHashMap<>();

    @Getter
    private Map<String, HandleMapping> requestMappingMap;

    @Getter
    private List<Class<?>> interceptorList;

    private List<RequestProcessor> PROCESSOR = new ArrayList<RequestProcessor>();
    @Override
    public void init() throws ServletException {
        //初始化IOC容器
        this.beanContainer = BeanContainer.getInstance();
        //扫描com.coco下面的包，然后将对象注册进容器中
        String[] basePackages = new String[]{"com.coco","org.coco.mybatis"};
        this.beanContainer.loadBeans(basePackages);
        dependencyInjector.doIoc();
        //然后解析所有注册好的bean，判断该Bean上面是否有@Controller注解
        Set<Class<?>> classes = parseBeanIsHaveControllerClass(beanContainer.getClasses());
        //然后继续解析这些带有@Controller注解的类，将类中方法和请求路径放入到一个Map中去
        this.requestMappingMap = parseUrlAndPath(classes);
        this.interceptorList = this.beanContainer.getInterceptorList();
        //初始化请求处理器责任链
        PROCESSOR.add(new PreRequestProcessor());
        PROCESSOR.add(new StaticRequestProcessor(getServletContext()));
        PROCESSOR.add(new JspRequestProcessor(getServletContext()));
        PROCESSOR.add(new ControllerRequestProcessor(this));
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //创建责任链对象实例
        RequestProcessorChain requestProcessorChain = new RequestProcessorChain(PROCESSOR.iterator(),req,resp);
        //通过责任链模式来依次调用请求处理器对请求做处理
        requestProcessorChain.doRequestProcessorChain();
        //对处理结果进行渲染
        requestProcessorChain.doRender();
    }

    private Set<Class<?>> parseBeanIsHaveControllerClass(Set<Class<?>> classes) {
        Set<Class<?>> result = new HashSet<>();
        if(ValidationUtil.isEmpty(classes)) {
            return null;
        }
        for(Class clazz:classes) {
            //如果这个类上面有@Controller注解,就把这个对象加入到Set集合当中去
             if(clazz.isAnnotationPresent(Controller.class)) {
                 result.add(clazz);
             }
        }
        return result;
    }

    private Map<String,HandleMapping> parseUrlAndPath(Set<Class<?>> classes) {

        if(ValidationUtil.isEmpty(classes)) {
            return urlAndPathMap;
        }
        for(Class<?> clazz : classes) {


            //判断这个类上面是否还有@RequestMapping注解，如果有，就取出value值
            if(clazz.isAnnotationPresent(RequestMapping.class)) {
                RequestMapping annotation = clazz.getAnnotation(RequestMapping.class);
                String requestMappingValue = annotation.value();
                //然后遍历该类中的所有方法,这里只取出public方法
                Method[] methods = clazz.getDeclaredMethods();
                for(Method method : methods) {
                    HandleMapping handleMapping = new HandleMapping();
                    //遍历所有的public方法，查看是否有@RequestMapping
                    String methodOnRequestMappingValue = null;
                    RequestMapping requestAnnotation = method.getAnnotation(RequestMapping.class);
                    if(annotation != null) {
                        methodOnRequestMappingValue = requestAnnotation.value();
                        if(urlAndPathMap.containsKey(requestMappingValue+methodOnRequestMappingValue)) {
                            throw new RuntimeException(requestMappingValue + " already exist");
                        }
                        handleMapping.setClazz(clazz);
                        handleMapping.setMethod(method.getName());
                        handleMapping.setPath(requestMappingValue+methodOnRequestMappingValue);
                        urlAndPathMap.put(requestMappingValue+methodOnRequestMappingValue,handleMapping);
                    }
                }
            }else {
                //说明该类上没有@RequestMapping注解
                Method[] methods = clazz.getMethods();
                //putUrlAndPathToMap(methods,null);
                for(Method method : methods) {
                    HandleMapping handleMapping = new HandleMapping();
                    //遍历所有的public方法，查看是否有@RequestMapping
                    String methodOnRequestMappingValue = null;
                    RequestMapping requestAnnotation = method.getAnnotation(RequestMapping.class);
                    if(requestAnnotation != null) {
                        methodOnRequestMappingValue = requestAnnotation.value();
                        if(urlAndPathMap.containsKey(methodOnRequestMappingValue)) {
                            throw new RuntimeException(methodOnRequestMappingValue + " already exist");
                        }
                        handleMapping.setClazz(clazz);
                        handleMapping.setMethod(method.getName());
                        handleMapping.setPath(methodOnRequestMappingValue);
                        urlAndPathMap.put(methodOnRequestMappingValue,handleMapping);
                    }
                }
            }
        }
        return urlAndPathMap;
    }
}
